package lab.chapter;

import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;

/**
 * 字符集合
 * 用于在词法分析时，定义满足条件的字符。
 * 1. 代表一个字符
 * 2. 用起止字符定义一个集合
 * 3. 对集合取补集
 * 4. 通过包含多个子集合来定义该集合
 */
public class CharSet {

    // 起始字符
    protected Character fromChar = null;

    //终止字符
    protected Character toChar = null;

    //是否取补集， 如 [^a]
    protected boolean exclude = false;

    //子集
    protected List<CharSet> subSets = null;

    //ascii 表 ， 0 - 127
    public static List<Character> ascii = ascii();
    //字母和数字
    public static List<Character> letterAndDigits = letterAndDigits();
    //整个字母表
    public static List<Character> Alphabet = ascii;

    protected CharSet(Character fromChar, Character toChar, boolean exclude) {
        this.fromChar = fromChar;
        this.toChar = toChar;
        this.exclude = exclude;
    }

    protected CharSet(Character fromChar) {
        this(fromChar, fromChar, false);
    }

    protected CharSet(Character fromChar, Character toChar) {
        this(fromChar, toChar, false);
    }

    protected CharSet() {}

    protected void addSubSet(CharSet charSet) {
        if (subSets == null) {
            subSets = new LinkedList<>();
        }
        subSets.add(charSet);
    }

    //常量
    // 数字
    public static CharSet digit = new CharSet('0', '9');
    // 小写字母
    public static CharSet smallLetter = new CharSet('a', 'z');
    // 大写字母
    public static CharSet capitalLetter = new CharSet('A', 'Z');
    // 大小写字母
    public static CharSet letter = initLetter();
    // 字母+数字
    public static CharSet letterOrDigit = initLetterOrDigit();
    // 空白字符
    public static CharSet whiteSpace = initWhiteSpace();

    /**
     * 某个字符是否属于本集合
     * @param character 字符
     * @return boolean
     */
    public boolean match(Character character) {
        boolean rtn = false;
        if (subSets != null) {
            for (CharSet subSet : subSets) {
                rtn = subSet.match(character);
                if (rtn) {
                    break;
                }
            }
        } else {
            rtn = (fromChar <= character && character <= toChar);
        }
        // 取反
        if (exclude) {
            rtn = !rtn;
        }
        return rtn;
    }

    /**
     * 递归输出规则
     * @return string
     */
    @Override
    public String toString() {
        if (subSets != null) {
            StringBuilder sb = new StringBuilder();
            if (exclude) {
                sb.append('^');
                if (subSets.size() > 1) {
                    sb.append('(');
                }
            }
            for (int i = 0; i < subSets.size(); i++){
                if (i > 0) {
                    sb.append('|');
                }
                // 此处相当于subSets.get(i).toString()
                sb.append(subSets.get(i));
            }
            if (exclude && subSets.size() > 1) {
                sb.append(')');
            }
            return sb.toString();
        } else if (fromChar == toChar) {
            return String.valueOf(fromChar);
        } else {
            if (exclude) {
                return "[^" + fromChar + "-" + toChar + "]";
            } else {
                return "[" + fromChar + "-" + toChar + "]";
            }
        }
    }

    // 是否是空集
    public boolean isEmpty() {
        if (subSets != null) {
            boolean empty = true;
            for (CharSet charSet : subSets) {
                if (!charSet.isEmpty()) {
                    empty = false;
                    break;
                }
            }
            return empty;
        }
        return fromChar == null;
    }

    @Override
    public boolean equals(Object object) {
        CharSet charSet = (CharSet) object;
        for (Character ch : Alphabet) {
            if (charSet.match(ch)) {
                if (!match(ch)) {
                    // 不相等
                    return false;
                }
            } else {
                if (match(ch)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 返回一个等价的集合，但显示更简短
     * 如 a|b|。。。z,显示成[a-z]
     * @return string
     */
    public CharSet getShorterForm() {
        if (this.equals(digit)) {
            return digit;
        } else if (this.equals(smallLetter)) {
            return smallLetter;
        } else if (this.equals(capitalLetter)) {
            return capitalLetter;
        } else if (this.equals(letter)) {
            return letter;
        } else if (this.equals(letterOrDigit)) {
            return letterOrDigit;
        } else {
            CharSet charSet = getSupplementarySet();
            charSet.exclude = true;
            return charSet;
        }
    }

    /**
     * 计算补集
     * @return CharSet
     */
    private CharSet getSupplementarySet() {
        CharSet charSet = new CharSet();
        for (Character ch: letterAndDigits) {
            if (!match(ch)) {
                charSet.addSubSet(new CharSet(ch));
            }
        }
        if (charSet.subSets.size() == 0) {
            charSet = letterOrDigit;
        }
        return charSet;
    }

    /**
     * 初始化字母表。
     * @return 0-127
     */
    private static List<Character> ascii() {
        List<Character> Alphabet = new LinkedList<>();
        for (int i = 0; i < 128; i++) {
            Alphabet.add((char)i);
        }
        return Alphabet;
    }

    /**
     * 包含字母、数字的字母表
     * @return 字母 + 数字
     */
    private static List<Character> letterAndDigits() {
        List<Character> Alphabet = new LinkedList<>();
        for (char i = '0'; i <= '9'; i++) {
            Alphabet.add(i);
        }

        for (char i = 'A'; i <= 'Z'; i++) {
            Alphabet.add(i);
        }

        for (char i = 'a'; i <= 'z'; i++) {
            Alphabet.add(i);
        }
        return Alphabet;
    }

    private static CharSet initLetter() {
        CharSet charSet = new CharSet();
        charSet.addSubSet(smallLetter);
        charSet.addSubSet(capitalLetter);
        return charSet;
    }

    private static CharSet initLetterOrDigit() {
        CharSet charSet = new CharSet();
        charSet.addSubSet(digit);
        charSet.addSubSet(smallLetter);
        charSet.addSubSet(capitalLetter);
        return charSet;
    }

    private static CharSet initWhiteSpace() {
        CharSet charSet = new CharSet();
        charSet.addSubSet(new CharSet(' '));
        charSet.addSubSet(new CharSet('\t'));
        charSet.addSubSet(new CharSet('\n'));
        return charSet;
    }
}
